#include "mainwindow.h"
#include <qmath.h>

static int rate = 33;

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    t(0), maxForceY(0), maxMovementY(0), maxTrajectoryX(0), maxTrajectoryY(0)
{
    ui->setupUi(this);

    connect(ui->massSlider, SIGNAL(valueChanged(int)),
            this, SLOT(updateMassFromSlider()));
    connect(ui->springFactorSlider, SIGNAL(valueChanged(int)),
            this, SLOT(updateSpringFactorFromSlider()));
    connect(ui->dampingFactorSlider, SIGNAL(valueChanged(int)),
            this, SLOT(updateDampingFactorFromSlider()));

    connect(ui->massTextbox, SIGNAL(returnPressed()),
            this, SLOT(updateMass()));
    connect(ui->springFactorTextbox, SIGNAL(returnPressed()),
            this, SLOT(updateSpringFactor()));
    connect(ui->dampingFactorTextbox, SIGNAL(returnPressed()),
            this, SLOT(updateDampingFactor()));

    connect(ui->x0Textbox, SIGNAL(returnPressed()),
            this, SLOT(updateX0()));
    connect(ui->v0Textbox, SIGNAL(returnPressed()),
            this, SLOT(updateV0()));
    connect(ui->deltaTextbox, SIGNAL(returnPressed()),
            this, SLOT(updateX0()));

    connect(ui->hConstRadiobtn, SIGNAL(toggled(bool)),
            this, SLOT(updatehOption()));
    connect(ui->hSignRadiobtn, SIGNAL(toggled(bool)),
            this, SLOT(updatehOption()));
    connect(ui->hCosRadiobtn, SIGNAL(toggled(bool)),
            this, SLOT(updatehOption()));
    connect(ui->hATextbox, SIGNAL(returnPressed()),
            this, SLOT(updatehA()));
    connect(ui->hOmegaTextbox, SIGNAL(returnPressed()),
            this, SLOT(updatehOmega()));
    connect(ui->hFiTextbox, SIGNAL(returnPressed()),
            this, SLOT(updatehFi()));

    connect(ui->wConstRadiobtn, SIGNAL(toggled(bool)),
            this, SLOT(updatewOption()));
    connect(ui->wSignRadiobtn, SIGNAL(toggled(bool)),
            this, SLOT(updatewOption()));
    connect(ui->wCosRadiobtn, SIGNAL(toggled(bool)),
            this, SLOT(updatewOption()));
    connect(ui->wATextbox, SIGNAL(returnPressed()),
            this, SLOT(updatewA()));
    connect(ui->wOmegaTextbox, SIGNAL(returnPressed()),
            this, SLOT(updatewOmega()));
    connect(ui->wFiTextbox, SIGNAL(returnPressed()),
            this, SLOT(updatewFi()));

    connect(ui->GLView, SIGNAL(x0changed()),
            this, SLOT(x0change()));

    physics = new Physics();
    physics->restart(ui->x0Textbox->text().toDouble(),
                     ui->v0Textbox->text().toDouble(),
                     ui->deltaTextbox->text().toDouble());

    ui->GLView->updateX(ui->x0Textbox->text().toDouble());

    setupMovementGraphs(ui->movementPlot);
    setupForcesGraphs(ui->forcesPlot);
    setupTrajectoryGraph(ui->trajectoryPlot);

    // setup a timer that repeatedly calls MainWindow::realtimeDataSlot:
    connect(&dataTimer, SIGNAL(timeout()), this, SLOT(dataSlot()));
    dataTimer.start(rate);//physics->delta * 1000.0); // Interval 0 means to refresh as fast as possible
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_actionExit_triggered()
{
    exit(0);
}

void MainWindow::task5()
{
    dataTimer.stop();

    QEventLoop loop;
    connect(this, SIGNAL(found()), &loop, SLOT(quit()));

    ui->x0Textbox->setText("1");
    ui->v0Textbox->setText("0");

    ui->massTextbox->setText("1");
    physics->mass = 1;
    updateMass();

    ui->springFactorTextbox->setText("1");
    physics->cSpringFactor = 1;
    updateSpringFactor();

    ui->wATextbox->setText("1");
    physics->wA = 1;
    ui->wCosRadiobtn->click();

    double kTab[4] = {1.0, 0.5, 0.1, 0.01};
    const char* finames[4] = {"fi1.bmp", "fi05.bmp", "fi01.bmp", "fi001.bmp"};
    const char* Anames[4] = {"A1.bmp", "A05.bmp", "A01.bmp", "A001.bmp"};
    for(int i = 0; i < 4; i++)
    {
        dataTimer.stop();
        double kTmp = kTab[i];
        ui->dampingFactorTextbox->setText(doubleToConstChar(kTmp));
        updateDampingFactor();
        physics->kEnvironmentFactor = kTmp;

        double wTmp = 0.0;
        //double* fis = new double[10];
        //double* As = new double[10];
        QCustomPlot* tmpPlotFi = new QCustomPlot();
        QCustomPlot* tmpPlotA = new QCustomPlot();
        tmpPlotFi->addGraph();
        tmpPlotA->addGraph();
        for(int j = 0; j < 10; j++, wTmp += 0.5)
        {
            q = 0;
            qx = 0;
            A = 0;
            ui->wOmegaTextbox->setText(doubleToConstChar(wTmp));
            physics->wOmega = wTmp;

            restartSimulation();
            loop.exec();
            //dataTimer.stop();
            //fis[j] = q;
            //As[j] = A;
            tmpPlotFi->graph(0)->addData(wTmp, q);
            tmpPlotFi->graph(0)->rescaleValueAxis();
            tmpPlotA->graph(0)->addData(wTmp, A);
            tmpPlotA->graph(0)->rescaleValueAxis();
        }
        tmpPlotFi->saveJpg(finames[i]);
        tmpPlotA->saveJpg(Anames[i]);
        tmpPlotFi->close();
        tmpPlotA->close();

    }
    //dataTimer.start(rate);//physics->delta * 1000.0); // Interval 0 means to refresh as fast as possible
}

double MainWindow::updateParameterFromSlider(QSlider *slider, QLineEdit *lineEdit, double multiplier)
{
    double val = multiplier * (double)slider->value();
    lineEdit->setText(doubleToConstChar(val));
    return val;
}

double MainWindow::updateParameterFromTextBox(QLineEdit *lineEdit, QSlider *destSlider, double divisor)
{
    double val = lineEdit->text().toDouble();
    destSlider->setValue((int)(1 + val / divisor));
    return val;
}

void MainWindow::updateMassFromSlider()
{
    physics->mass = updateParameterFromSlider(ui->massSlider, ui->massTextbox, 0.2);
}

void MainWindow::updateMass()
{
    physics->mass = updateParameterFromTextBox(ui->massTextbox, ui->massSlider, 0.2);
}

void MainWindow::updateSpringFactorFromSlider()
{
    physics->cSpringFactor = updateParameterFromSlider(ui->springFactorSlider, ui->springFactorTextbox, 0.1);
}

void MainWindow::updateSpringFactor()
{
    physics->cSpringFactor = updateParameterFromTextBox(ui->springFactorTextbox, ui->springFactorSlider, 0.1);
}

void MainWindow::updateDampingFactorFromSlider()
{
    physics->kEnvironmentFactor = updateParameterFromSlider(ui->dampingFactorSlider, ui->dampingFactorTextbox, 0.01);
}

void MainWindow::updateDampingFactor()
{
    physics->kEnvironmentFactor = updateParameterFromTextBox(ui->dampingFactorTextbox, ui->dampingFactorSlider, 0.01);
}

const char* MainWindow::doubleToConstChar(double val)
{
    std::stringstream ss;
    ss << val;
    return ss.str().c_str();
}

double MainWindow::ConstCharToDouble(const char *s)
{
    std::stringstream ss(s);
    double *ret;
    ss >> *ret;
    return *ret;
}

void MainWindow::updatehOption()
{
    if(ui->hConstRadiobtn->isChecked())
    {
        physics->hOption = 0;
        return;
    }

    if(ui->hSignRadiobtn->isChecked())
    {
        physics->hOption = 1;
        return;
    }

    physics->hOption = 2;

    updatehA();
    updatehFi();
    updatehOmega();
}

void MainWindow::updatehA()
{
    physics->hA = ui->hATextbox->text().toDouble();
}

void MainWindow::updatehOmega()
{
    physics->hOmega = ui->hOmegaTextbox->text().toDouble();
}

void MainWindow::updatehFi()
{
    physics->hFi = ui->hFiTextbox->text().toDouble();
}

void MainWindow::updatewOption()
{
    if(ui->wConstRadiobtn->isChecked())
    {
        physics->wOption = 0;
        return;
    }

    if(ui->wSignRadiobtn->isChecked())
    {
        physics->wOption = 1;
        return;
    }

    physics->wOption = 2;

    updatewA();
    updatewFi();
    updatewOmega();
}

void MainWindow::updatewA()
{
    physics->wA = ui->wATextbox->text().toDouble();
    ui->GLView->updateW(physics->w(0));
}

void MainWindow::updatewOmega()
{
    physics->wOmega = ui->wOmegaTextbox->text().toDouble();
    ui->GLView->updateW(physics->w(0));
}

void MainWindow::updatewFi()
{
    physics->wFi = ui->wFiTextbox->text().toDouble();
    ui->GLView->updateW(physics->w(0));
}

void MainWindow::updateX0()
{
    physics->xCurr = ui->x0Textbox->text().toDouble();
    physics->xPrev = physics->xCurr;
    restartSimulation();
}

void MainWindow::updateV0()
{
    physics->v = ui->v0Textbox->text().toDouble();
    restartSimulation();
}

void MainWindow::updateDelta()
{
    physics->delta = ui->deltaTextbox->text().toDouble();
    restartSimulation();
}

void MainWindow::restartSimulation()
{
    //to do: restart simulation -> t=0, clear plots and refresh GLView
    dataTimer.stop();

    t = 0.0;
    ui->forcesPlot->graph(0)->clearData();
    ui->forcesPlot->graph(1)->clearData();
    ui->forcesPlot->graph(2)->clearData();
    ui->movementPlot->graph(0)->clearData();
    ui->movementPlot->graph(1)->clearData();
    ui->movementPlot->graph(2)->clearData();
    ui->movementPlot->graph(3)->clearData();
    ui->movementPlot->graph(4)->clearData();
    ui->trajectoryPlot->plottable(0)->clearData();

    ui->forcesPlot->yAxis->setRangeLower(-6);
    ui->forcesPlot->yAxis->setRangeUpper(6);
    ui->movementPlot->yAxis->setRangeLower(-6);
    ui->movementPlot->yAxis->setRangeUpper(6);
    ui->trajectoryPlot->yAxis->setRangeLower(-16);
    ui->trajectoryPlot->yAxis->setRangeUpper(16);
    ui->trajectoryPlot->xAxis->setRangeLower(-16);
    ui->trajectoryPlot->xAxis->setRangeUpper(16);

    ui->forcesPlot->replot();
    ui->movementPlot->replot();
    ui->trajectoryPlot->replot();

    maxForceY = minForceY = maxMovementY = minMovementY = maxTrajectoryX = minTrajectoryX =
            maxTrajectoryY = minTrajectoryY = 0;

    physics->restart(ui->x0Textbox->text().toDouble(),
                     ui->v0Textbox->text().toDouble(),
                     ui->deltaTextbox->text().toDouble());
    ui->GLView->updateX(physics->xPrev);

    dataTimer.start(rate);//physics->delta * 1000.0);
}

void MainWindow::setupMovementGraphs(QCustomPlot *plot)
{
    plot->legend->setVisible(true);
    QFont legendFont = font();
    legendFont.setPointSize(9);
    plot->legend->setFont(legendFont);

    for(int i = 0; i < 6; i++)
    {
        plot->addGraph();
        plot->graph(i)->setPen(QPen(color[i]));
        plot->graph(i)->setName(movementGraphsNames[i]);
    }

    plot->yAxis->setRangeLower(-6);
    plot->yAxis->setRangeUpper(6);
    plot->xAxis->setTickLabelType(QCPAxis::ltNumber);
    plot->xAxis->setLabel("time (s)");
    plot->xAxis->setAutoTickStep(false);
    plot->xAxis->setTickStep(2);
    plot->axisRect()->setupFullAxesBox();

    connect(plot->xAxis, SIGNAL(rangeChanged(QCPRange)), plot->xAxis2, SLOT(setRange(QCPRange)));
    connect(plot->yAxis, SIGNAL(rangeChanged(QCPRange)), plot->yAxis2, SLOT(setRange(QCPRange)));
}

void MainWindow::setupForcesGraphs(QCustomPlot *plot)
{
    plot->legend->setVisible(true);
    QFont legendFont = font();
    legendFont.setPointSize(9);
    plot->legend->setFont(legendFont);

    for(int i = 0; i < 3; i++)
    {
        plot->addGraph();
        plot->graph(i)->setPen(QPen(color[i]));
        plot->graph(i)->setName(forcesGraphsNames[i]);
    }

    plot->yAxis->setRangeLower(-6);
    plot->yAxis->setRangeUpper(6);
    plot->xAxis->setTickLabelType(QCPAxis::ltNumber);
    plot->xAxis->setLabel("time (s)");
    plot->xAxis->setAutoTickStep(false);
    plot->xAxis->setTickStep(2);
    plot->axisRect()->setupFullAxesBox();

    connect(plot->xAxis, SIGNAL(rangeChanged(QCPRange)), plot->xAxis2, SLOT(setRange(QCPRange)));
    connect(plot->yAxis, SIGNAL(rangeChanged(QCPRange)), plot->yAxis2, SLOT(setRange(QCPRange)));
}

void MainWindow::setupTrajectoryGraph(QCustomPlot *plot)
{
    //plot->addGraph();
    //plot->graph(0)->setPen(QPen(color[0]));

    plot->legend->setVisible(true);
    QFont legendFont = font();
    legendFont.setPointSize(9);
    plot->legend->setFont(legendFont);

    trajectoryPlot = new QCPCurve(plot->xAxis, plot->yAxis);
    trajectoryPlot->setPen((QPen(color[0])));
    plot->addPlottable(trajectoryPlot);
    plot->plottable(0)->setName(trajectoryGraphNames);

    plot->yAxis->setRangeLower(-16);
    plot->yAxis->setRangeUpper(16);
    plot->yAxis->setLabel("v(t)");

    plot->xAxis->setRangeLower(-16);
    plot->xAxis->setRangeUpper(16);
    plot->xAxis->setLabel("x(t)");
    plot->axisRect()->setupFullAxesBox();

    connect(plot->xAxis, SIGNAL(rangeChanged(QCPRange)), plot->xAxis2, SLOT(setRange(QCPRange)));
    connect(plot->yAxis, SIGNAL(rangeChanged(QCPRange)), plot->yAxis2, SLOT(setRange(QCPRange)));
}

void MainWindow::dataSlot()
{
    double max = 0, min = 0;
    double f = physics->f(t);
    double g = physics->g(t);
    double h = physics->h(t);
    double w = physics->w(t);


    if(ui->ftCheckbox->isChecked())
    {
        ui->forcesPlot->graph(0)->addData(t, f);
        ui->forcesPlot->graph(0)->removeDataBefore(t-8);
    }
    if(ui->gtCheckbox->isChecked())
    {
        ui->forcesPlot->graph(1)->addData(t, g);
        ui->forcesPlot->graph(1)->removeDataBefore(t-8);
    }
    if(ui->htCheckbox->isChecked())
    {
        ui->forcesPlot->graph(2)->addData(t, h);
        ui->forcesPlot->graph(2)->removeDataBefore(t-8);
    }

    max = qMax(f, g);
    max = qMax(max, h);
    if(maxForceY < max)
    {
        ui->forcesPlot->rescaleAxes(true);
        maxForceY = max;
    }
    min = qMin(f, g);
    min = qMin(min, h);
    if(minForceY > min)
    {
        ui->forcesPlot->rescaleAxes(true);
        minForceY = min;
    }

    if(ui->xtCheckBox->isChecked())
    {
        ui->movementPlot->graph(0)->addData(t, physics->xCurr);
        ui->movementPlot->graph(0)->removeDataBefore(t-8);
    }
    if(ui->vtCheckbox->isChecked())
    {
        ui->movementPlot->graph(1)->addData(t, physics->v);
        ui->movementPlot->graph(1)->removeDataBefore(t-8);
    }
    if(ui->atCheckbox->isChecked())
    {
        ui->movementPlot->graph(2)->addData(t, physics->a);
        ui->movementPlot->graph(2)->removeDataBefore(t-8);
    }
    if(ui->wtCheckbox->isChecked())
    {
        ui->movementPlot->graph(3)->addData(t, w);
        ui->movementPlot->graph(3)->removeDataBefore(t-8);
    }
    if(ui->xsxeCheckbox->isChecked())
    {
        ui->movementPlot->graph(4)->addData(t, fabs(physics->xCurr - physics->diffCurr));//physics->w(physics->innerT) - physics->xCurr));//
        ui->movementPlot->graph(4)->removeDataBefore(t-8);
    }
    if(ui->wtxtCheckbox->isChecked())
    {
        ui->movementPlot->graph(5)->addData(t, fabs(physics->w(physics->innerT) - physics->xCurr));//
        ui->movementPlot->graph(5)->removeDataBefore(t-8);
    }

    if(task5Flag)
    {
        if(physics->xCurr * physics->xPrev <= 0)
        {
            if(qx == 0)
            {
                qx = t;
            }
            else
            {
                double tmp = t - qx;
                qx = t;
                if(fabs(tmp - q) > 0.01)
                {
                    q = tmp;
                }
                else
                {
                    //mamy juz okres - teraz spraedzmy
                    dataTimer.stop();
                    double t0 = 0;
                    if(physics->xPrev > physics->xCurr)
                    {
                        t0 = t - q;
                    }
                    else
                    {
                        t0 = t;
                    }
                    while(t0 > 2.0 * q)
                    {
                        t0 -= 2.0 * q;
                    }

                    q = t0;
                    A = physics->xCurr / qSin(physics->wOmega * t);
                    emit found();
                }
            }
        }
    }

    max = qMax(physics->xCurr, physics->v);
    max = qMax(max, physics->a);
    max = qMax(max, w);
    if(maxMovementY < max)
    {
        ui->movementPlot->rescaleAxes(true);
        maxMovementY = max;
    }
    min = qMin(physics->xCurr, physics->v);
    min = qMin(min, physics->a);
    min = qMin(min, w);
    if(minMovementY > min)
    {
        ui->movementPlot->rescaleAxes(true);
        minMovementY = min;
    }

    trajectoryPlot->addData(physics->xCurr, physics->v);
    ui->GLView->updateX(physics->xCurr);
    ui->GLView->updateW(physics->w(t));


    if(maxTrajectoryX < physics->xCurr)
    {
        ui->trajectoryPlot->rescaleAxes(true);
        maxTrajectoryX = physics->xCurr;
    }
    if(maxTrajectoryY < physics->v)
    {
        ui->trajectoryPlot->rescaleAxes(true);
        maxTrajectoryY = physics->v;
    }
    if(minTrajectoryX > physics->xCurr)
    {
        ui->trajectoryPlot->rescaleAxes(true);
        minTrajectoryX = physics->xCurr;
    }
    if(minTrajectoryY > physics->v)
    {
        ui->trajectoryPlot->rescaleAxes(true);
        minTrajectoryY = physics->v;
    }

    t = t + (double)rate / 1000.0;
    physics->update(t);

    ui->forcesPlot->xAxis->setRange(t + 0.25, 8, Qt::AlignRight);
    ui->movementPlot->xAxis->setRange(t + 0.25, 8, Qt::AlignRight);
    ui->forcesPlot->replot();
    ui->movementPlot->replot();
    ui->trajectoryPlot->replot();
}

void MainWindow::on_actionStartSimulation_triggered()
{
    dataTimer.start();
}

void MainWindow::on_actionPauseSimulation_triggered()
{
    dataTimer.stop();
}

void MainWindow::on_actionStopSimulation_triggered()
{
    restartSimulation();
    dataTimer.stop();
}

void MainWindow::x0change()
{
    physics->restart(ui->GLView->m_x, MainWindow::ui->v0Textbox->text().toDouble(),
                     MainWindow::ui->deltaTextbox->text().toDouble());
    ui->x0Textbox->setText(doubleToConstChar(ui->GLView->m_x));
}

void MainWindow::on_actionTask5_triggered()
{
    dataTimer.stop();
    task5Flag = true;
    task5();
}
